﻿using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif

// ReSharper disable once CheckNamespace

namespace MugenMvvmToolkit.ViewModels
{
    /// <summary>
    ///     Represents the extensions method for view models.
    /// </summary>
    public static class ViewModelExtensions
    {
        #region Fields

        private static readonly object LockerAudit = new object();

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelExtensions"/> class.
        /// </summary>
        static ViewModelExtensions()
        {
            CreatedViewModelTypes = new List<Type>();
            InitializedViewModelTypes = new List<Type>();
            DisposedViewModelTypes = new List<Type>();
            FinalizedViewModelTypes = new List<Type>();
            CreatedViewModels = new WeakReferenceCollection<IViewModel>();
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the count of created view models. It's work only in debug mode.
        /// </summary>
        public static int CreatedViewModelCount { get; private set; }

        /// <summary>
        ///     Gets the count of initialized view models. It's work only in debug mode.
        /// </summary>
        public static int InitializedViewModelCount { get; private set; }

        /// <summary>
        ///     Gets the count of view models that was disposed using Dispose() method. It's work only in debug mode.
        /// </summary>
        public static int DisposedViewModelCount { get; private set; }

        /// <summary>
        ///     Gets the count of view models that was finalized. It's work only in debug mode.
        /// </summary>
        public static int FinalizedViewModelCount { get; private set; }

        /// <summary>
        ///     Gets the types of created view models. It's work only in debug mode.
        /// </summary>
        public static IList<Type> CreatedViewModelTypes { get; private set; }

        /// <summary>
        ///     Gets the types of initialized view models. It's work only in debug mode.
        /// </summary>
        public static IList<Type> InitializedViewModelTypes { get; private set; }

        /// <summary>
        ///     Gets the types of view models that was disposed using Dispose() method. It's work only in debug mode.
        /// </summary>
        public static IList<Type> DisposedViewModelTypes { get; private set; }

        /// <summary>
        ///     Gets the types of view models that was finalized. It's work only in debug mode.
        /// </summary>
        public static IList<Type> FinalizedViewModelTypes { get; private set; }

        /// <summary>
        /// Gets the list of created view-models. It's work only in debug mode.
        /// </summary>
        public static WeakReferenceCollection<IViewModel> CreatedViewModels { get; private set; }

        /// <summary>
        ///     Occurred after update any audit properties
        /// </summary>
        public static event Action<AuditAction> AuditInfoUpdated;

        #endregion

        #region View models extension

        /// <summary>
        ///     Wraps the specified view-model to a specified type.
        /// </summary>
        /// <param name="viewModel">The specified view-model.</param>
        /// <param name="parameters">The specified parameters, if any.</param>
        /// <returns>
        ///     An instance of <see cref="IViewModel"/>.
        /// </returns>
        public static T WrapTo<T>([NotNull]this IViewModel viewModel, IDictionary<string, object> parameters = null) where T : IViewModel
        {
            Should.NotBeNull(viewModel, "viewModel");
            return (T)viewModel.IocAdapter
                                .Get<IViewModelWrapperProvider>()
                                .WrapTo(viewModel, typeof(T), parameters);
        }

        /// <summary>
        ///     Wraps the specified view-model to a window.
        /// </summary>
        /// <param name="viewModel">The specified view-model.</param>
        /// <param name="parameters">The specified parameters, if any.</param>
        /// <returns>
        ///     An instance of <see cref="IWindowViewModel" />.
        /// </returns>
        public static IWindowViewModel WrapToWindow([NotNull]this IViewModel viewModel, IDictionary<string, object> parameters = null)
        {
            return WrapTo<IWindowViewModel>(viewModel, parameters);
        }

        /// <summary>
        ///     Wraps the specified view-model to a dialog.
        /// </summary>
        /// <param name="viewModel">The specified view-model.</param>
        /// <param name="parameters">The specified parameters, if any.</param>
        /// <returns>
        ///     An instance of <see cref="IDialogViewModel" />.
        /// </returns>
        public static IDialogViewModel WrapToDialog([NotNull]this IViewModel viewModel, IDictionary<string, object> parameters = null)
        {
            return WrapTo<IDialogViewModel>(viewModel, parameters);
        }

        /// <summary>
        ///Invokes an action after the current view model was initialized, if the view model is initialized action will invoked immediately.
        /// </summary>
        /// <param name="viewModel">The specified view model.</param>
        /// <param name="action">The specified delegate to invoke.</param>
        public static void InvokeAfterInitialization<T>([NotNull]this T viewModel, [NotNull] Action<T> action) where T : IViewModel
        {
            Should.NotBeNull(viewModel, "viewModel");
            Should.NotBeNull(action, "action");
            viewModel.InvokeAfterInitialization(model => action((T)model));
        }

        /// <summary>
        ///     Begins to indicate that the current view model is busy.
        /// </summary>
        /// <param name="viewModel">The specified view model.</param>
        /// <param name="message">
        ///    The specified message.
        /// </param>
        /// <returns>An instance of token.</returns>
        public static IDisposable BeginBusyOperation([NotNull]this IViewModel viewModel, object message = null)
        {
            Should.NotBeNull(viewModel, "viewModel");
            return new ActionToken(o => viewModel.EndBusy((Guid)o), viewModel.BeginBusy(message));
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="iocAdapter">The specified <see cref="IIocAdapter"/>.</param>
        /// <param name="getViewModelFunc">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure, Obsolete("Use the IViewModelProvider to create view-model")]
        public static T GetViewModel<T>([NotNull]this IIocAdapter iocAdapter, [NotNull] GetViewModelDelegate<T> getViewModelFunc, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
            where T : IViewModel
        {
            Should.NotBeNull(getViewModelFunc, "getViewModelFunc");
            return (T)GetViewModel(iocAdapter, getViewModel: adapter => getViewModelFunc(adapter), policy: policy,
                parentViewModel: parentViewModel, listenMode: listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="iocAdapter">The specified <see cref="IIocAdapter"/>.</param>
        /// <param name="getViewModel">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure, Obsolete("Use the IViewModelProvider to create view-model")]
        public static IViewModel GetViewModel([NotNull]this IIocAdapter iocAdapter, [NotNull] GetViewModelDelegate<IViewModel> getViewModel, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            return iocAdapter.Get<IViewModelProvider>().GetViewModel(getViewModel, policy, parentViewModel, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="iocAdapter">The specified <see cref="IIocAdapter"/>.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure, Obsolete("Use the IViewModelProvider to create view-model")]
        public static T GetViewModel<T>([NotNull]this IIocAdapter iocAdapter, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
            where T : IViewModel
        {
            return (T)GetViewModel(iocAdapter, typeof(T), policy, parentViewModel, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="viewModelType">The type of view model.</param>
        /// <param name="iocAdapter">The specified <see cref="IIocAdapter"/>.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>        
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure, Obsolete("Use the IViewModelProvider to create view-model")]
        public static IViewModel GetViewModel([NotNull]this IIocAdapter iocAdapter, [NotNull, ViewModelTypeRequired] Type viewModelType, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
        {
            Should.NotBeNull(iocAdapter, "iocAdapter");
            return iocAdapter.Get<IViewModelProvider>().GetViewModel(viewModelType, policy, parentViewModel, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="viewModelProvider">The specified <see cref="IViewModelProvider"/>.</param>
        /// <param name="getViewModelFunc">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure]
        public static T GetViewModel<T>([NotNull]this IViewModelProvider viewModelProvider, [NotNull] GetViewModelDelegate<T> getViewModelFunc, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
            where T : IViewModel
        {
            Should.NotBeNull(getViewModelFunc, "getViewModelFunc");
            return (T)GetViewModel(viewModelProvider, getViewModel: adapter => getViewModelFunc(adapter), policy: policy,
                parentViewModel: parentViewModel, listenMode: listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="viewModelProvider">The specified <see cref="IViewModelProvider"/>.</param>
        /// <param name="getViewModel">The specified delegate to create view model.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure]
        public static IViewModel GetViewModel([NotNull]this IViewModelProvider viewModelProvider, [NotNull] GetViewModelDelegate<IViewModel> getViewModel, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
        {
            Should.NotBeNull(viewModelProvider, "viewModelProvider");
            Should.NotBeNull(getViewModel, "getViewModel");
            if (policy == null)
                policy = GlobalSettings.DefaultActivationPolicy;
            if (listenMode.HasValue && listenMode.Value != policy.ListenMode)
                policy = new ActivationPolicy(GlobalSettings.DefaultActivationPolicy) { ListenMode = listenMode };
            return viewModelProvider.GetViewModel(getViewModel, policy, parentViewModel);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <typeparam name="T">The type of view model.</typeparam>
        /// <param name="viewModelProvider">The specified <see cref="IViewModelProvider"/>.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure]
        public static T GetViewModel<T>([NotNull]this IViewModelProvider viewModelProvider, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
            where T : IViewModel
        {
            return (T)GetViewModel(viewModelProvider, typeof(T), policy, parentViewModel, listenMode);
        }

        /// <summary>
        ///     Creates an instance of the specified view model.
        /// </summary>
        /// <param name="viewModelType">The type of view model.</param>
        /// <param name="viewModelProvider">The specified <see cref="IViewModelProvider"/>.</param>
        /// <param name="policy">The specified <see cref="IActivationPolicy"/>.</param>        
        /// <param name="parentViewModel">The specified parent view model, if any.</param>
        /// <param name="listenMode">The value that responsible for listen messages in created view model.</param>   
        /// <returns>
        ///     An instance of <see cref="IViewModel" />.
        /// </returns>
        [Pure]
        public static IViewModel GetViewModel([NotNull]this IViewModelProvider viewModelProvider, [NotNull, ViewModelTypeRequired]Type viewModelType, IActivationPolicy policy = null, IViewModel parentViewModel = null, ListenType? listenMode = null)
        {
            Should.NotBeNull(viewModelProvider, "viewModelProvider");
            Should.NotBeNull(viewModelType, "viewModelType");
            if (policy == null)
                policy = GlobalSettings.DefaultActivationPolicy;
            if (listenMode.HasValue && listenMode != policy.ListenMode)
                policy = new ActivationPolicy(GlobalSettings.DefaultActivationPolicy) { ListenMode = listenMode };
            return viewModelProvider.GetViewModel(viewModelType, policy, parentViewModel);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Notifies the system of view model events.
        /// </summary>
        public static void OnAuditInfoUpdated(AuditAction auditAction, [NotNull] IViewModel viewModel)
        {
            if (!GlobalSettings.IsDebugMode || GlobalSettings.IsDesignMode) return;
            using (DeadLockDetector.Lock(LockerAudit))
            {
                Type viewModelType = viewModel.GetType();
                switch (auditAction)
                {
                    case AuditAction.Created:
                        CreatedViewModelCount++;
                        CreatedViewModelTypes.Add(viewModelType);
                        CreatedViewModels.Add(viewModel);
                        break;
                    case AuditAction.Initialized:
                        InitializedViewModelCount++;
                        InitializedViewModelTypes.Add(viewModelType);
                        break;
                    case AuditAction.Disposed:
                        DisposedViewModelCount++;
                        DisposedViewModelTypes.Add(viewModelType);
                        CreatedViewModels.Remove(viewModel);
                        break;
                    case AuditAction.Finalized:
                        FinalizedViewModelCount++;
                        FinalizedViewModelTypes.Add(viewModelType);
                        break;
                }
            }
            Action<AuditAction> handler = AuditInfoUpdated;
            if (handler == null) return;
            handler(auditAction);
        }

        #endregion
    }
}